# koa-router中间件
30 秒速记
koa-router把「方法 + 路径 → 处理函数」做成一张表,支持/:id参数、前缀、嵌套和路由级中间件- 两个都要挂:
router.routes()负责分发,router.allowedMethods()负责405/501和OPTIONS - 包名变迁:老教程是
koa-router(Koa 2配7.x),现在维护的是@koa/router ctx.params里全是字符串而且是用户填的,转类型、校验、鉴权都要自己做- 路由级中间件也走洋葱模型,鉴权放在处理函数前面
koa-router 就是把原生路由里那一堆 if / switch 换成一张「方法 + 路径 → 处理函数」的表,它本身也是一个 Koa 中间件。 用法是 router.get('/user/:id', handler) 注册,最后 app.use(router.routes()).use(router.allowedMethods()) 挂上去。routes() 负责匹配和分发;allowedMethods() 负责路径存在但方法不对时回 405,方法压根不认识时回 501,还会自动应答 OPTIONS。包名要留意,老教程里是 koa-router,现在官方维护的是 @koa/router,用法基本一致,新项目装后者。
理解「koa-router中间件」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
一个带路径参数、前缀和路由级中间件的完整例子:
const Koa = require('koa')
const Router = require('@koa/router') // 老项目里是 require('koa-router')
const app = new Koa()
const router = new Router({ prefix: '/api' })
// 路由级中间件:只对挂了它的路由生效,先鉴权再进处理函数
const auth = async (ctx, next) => {
if (!ctx.get('Authorization')) ctx.throw(401, '未登录')
await next()
}
router.get('/users/:id', auth, ctx => {
const id = Number(ctx.params.id) // params 永远是字符串
if (!Number.isInteger(id)) ctx.throw(400, 'id 不合法')
ctx.body = { id }
})
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
// GET /api/users/1 带 Authorization -> {"id":1}
// GET /api/users/1 不带 -> 401 未登录
// GET /api/users/abc 带 Authorization -> 400 id 不合法
// DELETE /api/users/1 -> 405,Allow: HEAD, GET
执行顺序和 Koa 的洋葱模型一样:请求先过 app.use 注册的全局中间件,再进 router.routes(),匹配到路由后依次执行 auth、处理函数。allowedMethods() 放在 routes() 后面,只有在没有路由真正处理这个请求时,它才会根据匹配情况补上 405 / 501 或 OPTIONS 的响应。
面试官追问
追问 1只挂了 router.routes(),对一个只注册了 GET 的地址发 DELETE,返回什么?
404。挂上 allowedMethods() 之后变成 405 Method Not Allowed,响应头带 Allow: HEAD, GET;如果是 PURGE 这种路由器不认识的方法,回 501 Not Implemented。
追问 2/users/:id 拿到的 ctx.params.id,能直接拿去查库吗?
不能。它是字符串,而且是用户随便填的,/users/abc 也能进来。先转类型、校验格式,查库用参数化查询,再确认当前用户有没有权限看这个 id。
追问 3鉴权写在 app.use 里,还是写在路由上?
全站都要登录,就 app.use(auth) 放在 router.routes() 前面;只有部分接口要鉴权,就写成路由级中间件 router.get('/admin', auth, handler),或者给这组接口单独建一个 Router 再统一 use(auth)。
追问 4网上有的写 koa-router,有的写 @koa/router,有区别吗?
是同一个项目,后来迁到 koajs 组织下改名 @koa/router,旧包不再维护。用法基本一样,新项目装 @koa/router;升大版本时注意路径匹配规则变过,跑一遍路由测试再上线。
追问 5未知路径被某个兜底中间件回了 200,怎么查是谁写的响应?
在 router.routes() 后面加个打日志的中间件,打 ctx.path、ctx._matchedRoute 和 ctx.status。命中路由时 _matchedRoute 会是注册的路径模板,比如 /users/:id,是 undefined 就说明路由没命中,是后面的兜底接管的。
# koa-router中间件
如果依靠ctx.request.url去手动处理路由,将会写很多处理代码,这时候就需要对应的路由的中间件对路由进行控制,这里介绍一个比较好用的路由中间件koa-router
# 安装koa-router中间件
30 秒速记
Koa 2配koa-router@7:npm install --save koa-router@7;Koa 1的generator中间件配5.x- 不写版本会装最新主版本,和教程对不上;锁主版本,并提交
package-lock.json - 新项目直接
npm i @koa/router,旧包已不再维护 - 装完
npm ls koa-router看实际版本;部署用npm ci,以lock文件为准 npm 5+起install默认写进dependencies,--save可以省
Koa 2 用 koa-router 要装 7.x:npm install --save koa-router@7,版本号要写明,因为 5.x 是给 Koa 1 的 generator 中间件用的。 显式锁主版本,是为了团队每个人、包括 CI 装到的都是同一套 API。--save 现在可以省了,npm 5 以后默认就会写进 dependencies。新项目的话我会直接装 @koa/router,这是同一个库迁到 koajs 组织之后的名字,旧包已经不更新了。装完用 npm ls 看一眼实际装到的版本,别只看 package.json。
# koa2 对应的版本是 7.x
npm install --save koa-router@7
理解「安装koa-router中间件」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 middleware 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
面试官追问
追问 1不写版本号直接 npm install koa-router,会有什么问题?
会装到当时的最新版,主版本可能和教程不一样,细节对不上;不同时间装的人还可能装到不同版本。写死主版本,再把 package-lock.json 提交上去。
追问 2koa-router@7 和 @koa/router,该选哪个?
已经在用 7.x 的老项目没出问题就别折腾;新项目选 @koa/router,还在持续修复问题和安全漏洞。迁移时注意路径语法的变化,比如通配符的写法,先跑一遍路由测试。
追问 3package.json 里写的是 ^7.4.0,怎么知道线上到底装的哪个版本?
^7.4.0 允许装 7.x 里任何更高的版本,真正决定装哪个的是 lock 文件。看 npm ls koa-router,部署用 npm ci 严格按 lock 装,别用 npm install。
追问 4Koa 1 的老项目装了 koa-router@7,一启动就报错,为什么?
Koa 1 要求中间件是 function*,app.use 会直接拒绝 7.x 返回的普通函数中间件。Koa 1 就用 koa-router@5,或者把项目整体升到 Koa 2。
追问 5安装成功了,能说路由已经接好了吗?
不能,装上只是进了 node_modules
